﻿#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Threading;
using Castle.Windsor;
using Grawlix.Services;
using log4net;

namespace Grawlix.Client
{
	/// <summary>
	/// A game loader built for testing a specific game locally.
	/// </summary>
	[Serializable]
	public class TestGameLoader : ServiceBase<IGameLoader>,
	                              IGameLoader
	{
		/// <summary>
		/// The object used for logging errors.
		/// </summary>
		private static readonly ILog _log =
			LogManager.GetLogger("Grawlix.Client.TestGameLoader");

		/// <summary>
		/// The factory to create the game instance with.
		/// </summary>
		private readonly IGameKernelFactory _gameKernelFactory;

		/// <summary>
		/// The object to synchronize monitors on.
		/// </summary>
		private readonly object _lockObj = new object();

		/// <summary>
		/// The name of the world to load.
		/// </summary>
		private readonly string _world;

		/// <summary>
		/// The game that is currently being tested.
		/// </summary>
		private ExternalProxy<IGameKernel> _currentKernel;

		/// <summary>
		/// Whether a quit has been requested.
		/// </summary>
		private bool _hasQuit;

		/// <summary>
		/// Whether the game has been declared ready yet.
		/// </summary>
		private bool _ready;

		/// <summary>
		/// The kernel to use as parent.
		/// </summary>
		private IWindsorContainer _parentKernel;

		/// <summary>
		/// Initializes a new instance of the 
		/// <see cref="TestGameLoader"/> class.
		/// </summary>
		/// <param name="world">The name of the world to load.</param>
		/// <param name="gameKernelFactory">The game factory used to load the
		/// world.</param>
		/// <exception cref="ArgumentNullException"><c>gameKernelFactory</c>
		/// is null.</exception>
		/// <exception cref="ArgumentNullException"><c>world</c> is
		/// null.</exception>
		public TestGameLoader(string world,
		                      IGameKernelFactory gameKernelFactory)
		{
			if (gameKernelFactory == null)
				throw new ArgumentNullException("gameKernelFactory");
			if (world == null)
				throw new ArgumentNullException("world");
			_world = world;
			_gameKernelFactory = gameKernelFactory;
		}

		/// <summary>
		/// The kernel that is currently active.
		/// </summary>
		/// <value>The kernel that is currently active, or null if none
		/// are ready.</value>
		public IGameKernel CurrentKernel
		{
			get
			{
				lock (_lockObj)
				{
					if (_currentKernel == null ||
					    !_currentKernel.Target.Ready ||
					    _currentKernel.Target.Failure != null)
						return null;
					return _currentKernel.Target;
				}
			}
		}

		/// <summary>
		/// Gets a value indicating whether the user has requested to
		/// quit.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this the user has requested to quit;
		/// 	otherwise,
		/// <c>false</c>.
		/// </value>
		public bool HasQuit
		{
			get
			{
				Thread.MemoryBarrier();
				return _hasQuit;
			}
		}

		/// <summary>
		/// Activates using the specified parent kernel.
		/// </summary>
		/// <param name="parentKernel">The kernel to use as parent.
		/// </param>
		public void Activate(IWindsorContainer parentKernel)
		{
			_parentKernel = parentKernel;
			LoadLocal();
		}

		/// <summary>
		/// Cancels the current load.
		/// </summary>
		/// <exception cref="NotSupportedException">Canceling a load
		/// is not supported by the test loader.</exception>
		public void CancelLoad()
		{
			lock (_lockObj)
			{
				throw new NotSupportedException(
					"Canceling a load is not supported by the test loader.");
			}
		}

		/// <summary>
		/// Connects to a specified host.
		/// </summary>
		/// <param name="hostName">The name or IP address of the host.</param>
		/// <param name="port">The host's port.</param>
		/// <exception cref="ArgumentNullException"><c>hostName</c> is
		/// null.</exception>
		/// <exception cref="ArgumentException">port must be between 1
		/// and 65535, inclusive.</exception>
		/// <exception cref="NotSupportedException">Joining a network
		/// game is not supported by the test loader.</exception>
		public void Connect(string hostName, int port)
		{
			if (hostName == null)
				throw new ArgumentNullException("hostName");
			lock (_lockObj)
			{
				if ((port > 0) && (port <= 0xffff))
				{
					throw new NotSupportedException(
						"Joining a network game is not supported by the test loader.");
				}
				throw new ArgumentException(
					"Valid ports are between 1 and 65535, inclusive. " +
					port + " was specified.", "port");
			}
		}

		/// <summary>
		/// Hosts the world with the specified name.
		/// </summary>
		/// <param name="worldName">Name of the world to load.</param>
		/// <param name="port">The port to host on.</param>
		/// <exception cref="ArgumentNullException"><c>worldName</c> is
		/// null.</exception>
		/// <exception cref="NotSupportedException">Loading a network
		/// game is not supported on the test loader.</exception>
		/// <exception cref="ArgumentException">port must be between 1
		/// and 65535, inclusive.</exception>
		public void Host(string worldName, int port)
		{
			if (worldName == null)
				throw new ArgumentNullException("worldName");
			lock (_lockObj)
			{
				if ((port > 0) && (port <= 0xffff))
				{
					throw new NotSupportedException(
						"Hosting a network game is not supported by the test loader.");
				}
				throw new ArgumentException(
					"Valid ports are between 1 and 65535, inclusive. " +
					new StringBuilder().Append(port).Append(
						" was specified."), "port");
			}
		}

		/// <summary>
		/// Loads the local game.
		/// </summary>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification =
				"Exceptions should not propagate past this point.")]
		public void LoadLocal()
		{
			lock (_lockObj)
			{
				try
				{
					if (CurrentKernel != null)
					{
						if (_log.IsWarnEnabled)
						{
							_log.Warn("Received a second request to " +
							          "load the local game. " + "Quitting.");
						}
						Quit();
					}
					string configFile = "World/" + _world +
					                    "/Client/Properties/ServiceConfiguration.boo";
					string searchPaths = "World/" + _world +
					                     "/Client;Library/Client";
					_currentKernel = _gameKernelFactory.Create(_world, _parentKernel,
					                                           configFile,
					                                           searchPaths);
				}
				catch (Exception exception)
				{
					Quit();
					if (_log.IsWarnEnabled)
					{
						_log.Warn((("An error occurred in " +
						            new StringBuilder("World ").Append(_world)
						            	.Append(" during load. ")) +
						           "Quitting.\n") + exception);
					}
				}
			}
		}

		/// <summary>
		/// Indicate that the game should quit.
		/// </summary>
		public void Quit()
		{
			_hasQuit = true;
			Thread.MemoryBarrier();
			if (_log.IsDebugEnabled)
				_log.Debug("Quit was requested.");
		}

		/// <summary>
		/// Updates the current game based on the time elapsed.
		/// </summary>
		/// <param name="elapsed">The elapsed time since last update.
		/// </param>
		/// <returns>The game to switch to.</returns>
		public void Update(TimeSpan elapsed)
		{
			lock (_lockObj)
			{
				if (_currentKernel == null)
				{
					if (_log.IsWarnEnabled)
					{
						_log.Warn("Game null during update in test loader. " +
						          "Quitting.");
					}
					Quit();
					return;
				}
				IGameKernel current = _currentKernel.Target;
				if (current.Ready)
				{
					if (_ready)
					{
						if (_log.IsInfoEnabled)
							_log.Info("World" + _world + " is loaded and ready.");
						_ready = true;
					}
					if (current.Failure != null)
					{
						if (_log.IsFatalEnabled)
							_log.Fatal("Game has failed to load.",
							           current.Failure);
						_currentKernel = null;
						Quit();
					}
				}
			}
		}
	}
}